#########################
#   Author : Leon yu    #
#   Date : 2025/06/03   #
#   Id : SM2772         #
#########################
import os
import time
from datetime import datetime, timedelta
import requests
import base64
import logging
from logging.handlers import TimedRotatingFileHandler
import colorlog
import json
import pymysql
from string import Template
from Crypto.Cipher import DES
from Crypto.Util.Padding import pad
import toml
from configHandle import *

# # 配置读取
# currentPath = os.path.abspath(os.path.dirname(__file__))
# confLib = os.path.join(currentPath, 'conf')
# baseConfPath = os.path.join(confLib, 'baseConf.toml')
# apisConfPath = os.path.join(confLib, 'apisConf.toml')
# baseConfig = toml.load(baseConfPath)
# apisConfig = toml.load(apisConfPath)

class Utils(object):
    businessEnv = selectBusinessEnv()
    businessName = selectBusiness()
    apiEnv = baseConfig['businessType'][businessName]['apiEnv']
    apiMethod = ["GET", "POST"]
    desInfo = baseConfig['desInfo'][apiEnv]
    apiRegion = baseConfig['businessType'][businessName]['apiRegion']
    baseConfInfo = baseConfig['businessType'][businessName]
    apisConfInfo = apisConfig['businessType'][businessName]
    apisInfo = apisConfInfo['apis']
    commonHeaders = {"Content-Type":"application/json"}
    loginName = baseConfInfo['userName']
    loginPassword = baseConfInfo['passWord']
    appId = baseConfInfo['appId']
    appKey = baseConfInfo['appKey']
    orgId = baseConfInfo['orgId']
    commonHost = baseConfInfo['host']
    businessEnv = businessEnv
    
    def tuple2Str(data:tuple):
        return f'({str(data).strip("(),")})'
    
    @classmethod
    def __init__(self):
        # self.apiPath = apiPath
        # self.businessName = self.businessName
        # self.apiEnv = baseConfig['businessType'][self.businessName]['apiEnv']
        # self.apiMethod = ["GET", "POST"]
        # self.desInfo = baseConfig['desInfo'][self.apiEnv]
        # self.apiRegion = baseConfig['businessType'][self.businessName]['apiRegion']
        # self.baseConfInfo = baseConfig['businessType'][self.businessName]
        # self.apisConfInfo = apisConfig['businessType'][self.businessName]
        # self.apisInfo = self.apisConfInfo['apis']
        # self.commonHeaders = {"Content-Type":"application/json"}
        # self.loginName = self.baseConfInfo['userName']
        # self.loginPassword = self.baseConfInfo['passWord']
        # self.appId = self.baseConfInfo['appId']
        # self.appKey = self.baseConfInfo['appKey']
        # self.orgId = self.baseConfInfo['orgId']
        # self.commonHost = self.baseConfInfo['host']
        # self.businessEnv = self.businessEnv
        # self.haha = "123"
        pass
    
    # 模板替换
    @staticmethod
    def template(data:str, change:dict={}):
        if len(change) > 0:
            template = Template(data)
            template_data = template.safe_substitute(change)
            return template_data
        else:
            return data
    
    # apiInfo数据加工
    @staticmethod
    def apiInfoProcess(req_info:dict):
        if not req_info or req_info is None:
            CommonLog.error(f"def api_req -> req_info : {req_info}")
            return 
        try:
            apiMethod = req_info['method']
            if apiMethod not in Utils.apiMethod:
                CommonLog.error(f"def api_req -> req_info : {req_info}")
                return 
            else:
                pass
        except Exception as e:
            CommonLog.error(f"req_info is wrong -> {e}")
            return
    
    @staticmethod
    def encrypt(data, key, iv):
        try:
            iv_spec = iv.encode()
            secret_spec = key.encode()

            cipher = DES.new(secret_spec, DES.MODE_CBC, iv_spec)
            padded_data = pad(data.encode(), DES.block_size)
            encrypted = cipher.encrypt(padded_data)
            return base64.b64encode(encrypted).decode()
        except Exception as e:
            raise Exception(f"encrypt is Wrong {e}")
        
    @classmethod
    def requestClass(reqeust:dict={}, context:dict={}):
        reqBody = {
            "request": reqeust,
            "context": context
        }
        return reqBody
    
    @classmethod
    def bussinessRequestClass(cls, apiInfo:dict, header:dict, body:dict):
        uri = apiInfo['path']
        method = apiInfo['method'].lower()
        serviceName = apiInfo['serviceName']
        reqBody = {
            "config": {
                "requestTimeout": 30000,
                "serviceName": serviceName
            },
            "httpData": {
                "uri": uri,
                "method": method,
                "header": header,
                "body": body
            }
        }
        return reqBody
    
    @classmethod
    def bussinessRequestHost(cls):
        loginHost = cls.template(cls.commonHost, {"region" : f"{cls.apiRegion}.{cls.apiEnv}"})
        if cls.apiEnv == "onl":
            loginHost = cls.template(cls.commonHost, {"region" : f"{cls.apiRegion}"})
        return loginHost
    
    @classmethod
    def commonReqFunc(self, method:str, url, headers, body):
        if method.lower() == "get":
            req = requests.get(url=url, headers=headers, params=body)
            return json.loads(req.text)
        if method.lower() == "post":
            req = requests.post(url=url, headers=headers, json=body)
            return req.json()
            
    # 云登录,获取refreshToken,userId
    @classmethod
    def login2Cloud(cls):
        password = cls.encrypt(cls.loginPassword, cls.desInfo['desKey'], cls.desInfo['desIv'])
        loginBody = {
                    "request": {
                        "validityTime": 0,
                        "password": password,
                        "userName": cls.loginName,
                        "strategy": 1
                    },
                    "context": {
                        "appId": cls.appId,
                        "orgId": cls.orgId
                    }
                }
        loginPath = cls.apisInfo['loginByLoginName']['path']
        loginMethod = cls.apisInfo['loginByLoginName']['method']
        loginHost = cls.template(cls.commonHost, {"region" : f"{cls.apiRegion}.{cls.apiEnv}"})
        if cls.apiEnv == "onl":
            loginHost = cls.template(cls.commonHost, {"region" : f"{cls.apiRegion}"})
        loginUrl = f"https://{loginHost}{loginPath}"
        loginReq = cls.commonReqFunc(loginMethod, loginUrl, cls.commonHeaders, loginBody)
        try:
            if loginReq['code'] == "1":
                userInfo = {}
                userInfo['userId'] = loginReq['data']['userId']
                userInfo['refreshToken'] = loginReq['data']['refreshToken']
                return userInfo
            else:
                raise Exception(f"loginByLoginName is Error {loginReq}")
        except Exception as e:
            raise Exception(f"loginByLoginName Exception {e}")
        
    @classmethod
    def getAccessToken(cls, refreshToken):
        getAccessTokenBody = {
                "request": {
                    "refreshToken": refreshToken
                },
                "context": {
                    "appId": cls.appId,
                    "orgId": cls.orgId
                }
            }
        getAccessTokenPath = cls.apisInfo['getAccessToken']['path']
        getAccessTokenMethod = cls.apisInfo['getAccessToken']['method']
        getAccessTokenHost = cls.template(cls.commonHost, {"region" : f"{cls.apiRegion}.{cls.apiEnv}"})
        if cls.apiEnv == "onl":
            getAccessTokenHost = cls.template(cls.commonHost, {"region" : f"{cls.apiRegion}"})
        getAccessTokenUrl = f"https://{getAccessTokenHost}{getAccessTokenPath}"
        getAccessTokenReq = cls.commonReqFunc(getAccessTokenMethod, getAccessTokenUrl, cls.commonHeaders, getAccessTokenBody)
        try:
            if getAccessTokenReq['code'] == "1":
                accessToken = getAccessTokenReq['data']['accessToken']
                return accessToken
            else:
                raise Exception(f"getAccessToken is Error {getAccessTokenReq}")
        except Exception as e:
            raise Exception(f"getAccessToken Exception {e}")
        
    @classmethod
    def writeData(cls, data, filePath:str="", jsonFormat=False):
        tempFilesPath = os.path.join(currentPath, 'tempFiles')
        tempFilePath = os.path.join(tempFilesPath, filePath)
        if not os.path.exists(tempFilesPath):
            os.makedirs(tempFilesPath)
        if jsonFormat:
            with open(tempFilePath, 'w') as file:
                json.dump(data, file)
        else:
            with open(tempFilePath, 'w') as file:
                file.write(data)
        return
    
    @classmethod
    def loadData(cls, filePath:str="", jsonFormat=False):
        tempFilesPath = os.path.join(currentPath, 'tempFiles')
        tempFilePath = os.path.join(tempFilesPath, filePath)
        if (not os.path.exists(tempFilesPath)) or (not os.path.exists(tempFilePath)):
            CommonLog.error(f"{tempFilesPath} or {tempFilePath} is not exist")
            raise Exception("loadData is Fail")
        if jsonFormat:
            with open(tempFilePath, 'r') as file:
                loaded_data = json.load(file)
        else:
            with open(tempFilePath, 'r') as file:
                loaded_data = file.read()
        return loaded_data
    
    @classmethod
    def isNowDate(cls, startTime:int, endTime:int):
        startTime_timestampSeconds = int(startTime / 1000)
        endTime_timestampSeconds = int(endTime / 1000)
        startTime_dateTime = datetime.fromtimestamp(startTime_timestampSeconds).strftime('%Y-%m-%d')
        endTime_dateTime = datetime.fromtimestamp(endTime_timestampSeconds).strftime('%Y-%m-%d')
        # 当天date
        current_dateTime = datetime.utcnow().date()
        # 昨天date
        # yesterday_dateTime = current_dateTime - timedelta(days=1)
        # if startTime_dateTime != endTime_dateTime:
        #     raise Exception("startTime and endTime is not same day")
        # if startTime_dateTime != str(current_dateTime):
        #     raise Exception("startTime and endTime is not current datetime")
        if startTime_dateTime == str(current_dateTime):
            return 0
        if startTime_dateTime < str(current_dateTime):
            return 1
        
        
class CommonLog():
    @staticmethod
    def init():
        log_colors_config = {
            'DEBUG': 'cyan',
            'INFO': 'green',
            'WARNING': 'yellow',
            'ERROR': 'red',
            'CRITICAL': 'red',
        }
        currentPath = os.path.abspath(os.path.dirname(__file__))
        log_path = os.path.join(currentPath, 'Logs')
        now_t = time.strftime("%Y-%m-%d", time.localtime(int(time.time())))
        log_file = os.path.join(log_path, f'LogInfo_{now_t}.log')
        errorLog_file = os.path.join(log_path, f'LogError_{now_t}.log')
        logging.basicConfig(
            format='[%(asctime)s] %(filename)s -> %(funcName)s line:%(lineno)d [%(levelname)s] : %(message)s',
            level=logging.INFO
            )

        logger = logging.getLogger()
        logger.setLevel(logging.INFO)
        formatter = colorlog.ColoredFormatter('%(log_color)s[%(asctime)s] [%(filename)s:%(lineno)d] [%(module)s:%(funcName)s] [%(levelname)s]- %(message)s', log_colors=log_colors_config)
        new_formatter = '[%(levelname)s]%(asctime)s:%(msecs)s.%(process)d,%(thread)d#>[%(funcName)s]:%(lineno)s  %(message)s'
        fmt = logging.Formatter(new_formatter)

        log_handel = TimedRotatingFileHandler(log_file, when='MIDNIGHT')
        log_handel.setFormatter(fmt)
        logger = logging.getLogger('info')
        logger.setLevel(logging.INFO)
        logger.addHandler(log_handel)

        ##################### 错误日志 #########################
        Errorlogger = logging.getLogger()
        Errorlogger.setLevel(logging.ERROR)
        ErrorLog_handel = TimedRotatingFileHandler(errorLog_file, when='MIDNIGHT')
        ErrorLog_handel.setFormatter(fmt)
        Errorlogger = logging.getLogger('error')
        Errorlogger.setLevel(logging.ERROR)
        Errorlogger.addHandler(ErrorLog_handel)

    @staticmethod
    def console(level, message):
        logger = logging.getLogger('info')
        ch = logging.StreamHandler()
        ch.setLevel(logging.DEBUG)
        ch.setFormatter(colorlog.ColoredFormatter('%(log_color)s[%(asctime)s] [%(filename)s:%(lineno)d] [%(module)s:%(funcName)s] [%(levelname)s]- %(message)s'))
        logger.addHandler(ch)

        if level == 'info':
            logger.info(message)
        if level == 'debug':
            logger.debug(message)
        if level == 'warning':
            logger.warning(message)
        if level == 'error':
            Errorlogger = logging.getLogger('error')
            Errorlogger.error(message)
            Errorlogger.removeHandler(ch)
        logger.removeHandler(ch)
        return

    @staticmethod
    def debug(msg):
        CommonLog.console('debug', msg)

    @staticmethod
    def info(msg):
        CommonLog.console('info', msg)

    @staticmethod
    def warning(msg):
        CommonLog.console('warning', msg)

    @staticmethod
    def error(msg):
        CommonLog.console('error', msg)
        
        
class DatabaseControls(Utils):
    @classmethod
    def __init__(cls, buisnessName, env):
        cls.dbConnectInfo = baseConfig['businessType'][buisnessName]['DataBase'][env]
        cls.databaseMap = json.loads(baseConfig['businessType'][buisnessName]['DataBase']['databaseMap'])
        cls.host = cls.dbConnectInfo['dbHost']
        cls.port = cls.dbConnectInfo['dbPort']
        cls.user = cls.dbConnectInfo['dbUser']
        cls.password = cls.dbConnectInfo['dbPassword']
        cls.database = cls.dbConnectInfo['dbDatabase']
    
    @classmethod
    def choiceTable(cls, tableName):
        return cls.databaseMap[tableName]
    
    @classmethod
    def timestamp2Data(cls, timestamp:int):
        if len(str(timestamp)) != 13:
            CommonLog.error("timestamp fromat is Error")
            return
        timestamp /= 1000
        try:
            timestamp2Date = datetime.fromtimestamp(timestamp).strftime("%Y/%m/%d %H:%M:%S")
            return timestamp2Date
        except Exception as e:
            raise Exception(f"timestamp2Data is Faild {e}")
        
    @classmethod
    def db_connect(cls):
        conn = pymysql.connect(
            host=cls.host,
            user=cls.user,
            password=cls.password,
            database=cls.database,
            charset="utf8",
            port=cls.port,
            cursorclass=pymysql.cursors.DictCursor
            )
        try:
            cursor = conn.cursor()
            return {"conn": conn, "cursor": cursor}
        except Exception as e:
            CommonLog.error(f"db connect is Fail {e}")
            return
    
    # 字段、条件、聚合
    @classmethod
    def select(cls, tableName:str, columns:str="*", where:str="", aggregate:str=""):
        conn = cls.db_connect()['conn']
        cursor = cls.db_connect()['cursor']
        table = cls.choiceTable(tableName=tableName)
        sql = f"SELECT {columns} FROM {table}"
        if (len(where) != 0) and (len(aggregate) == 0):
            sql += f" WHERE {where}"
        elif (len(where) != 0) and (len(aggregate) != 0):
            sql += f" WHERE {where} {aggregate}"
        elif (len(where) == 0) and (len(aggregate) != 0):
            sql += f" {aggregate}"
        try:
            cursor.execute(sql)
            respSet = cursor.fetchall()
        except Exception as e:
            CommonLog.error(e)
        else:
            conn.commit()
        finally:
            cursor.close()
            conn.close()
        return respSet